import json
import sys
import traceback
from io import BytesIO

from re_common.baselibrary.utils.basedir import BaseDir
from re_common.baselibrary.utils.basefile import BaseFile

from apps.crawler_platform.util.sqlhelper import func_time, callback


class MongoDBHelper(object):
    def __init__(self):
        pass

    @classmethod
    @func_time(callback)
    async def find_one(cls, mongo_conn, filter, *args, **kwargs):
        """
        mongo_conn是到表级别的连接
        filter 是查询条件
        *args, **kwargs 与find 保持一致
        find: https://pymongo.readthedocs.io/en/3.12.0/api/pymongo/collection.html#pymongo.collection.Collection.find
        max_time_ms（可选）： max_time_ms 的值可以指定为**kwargs 的一部分，例如：
        return: 返回单个文档的字典对象,如果没有找到文档 返回None
        """
        result = await mongo_conn.find_one(filter, *args, **kwargs)
        return result

    @classmethod
    @func_time(callback)
    async def find(cls, mongo_conn, filter, *args, **kwargs):
        """
        mongo_conn是到表级别的连接
        filter 是查询条件
        *args, **kwargs 与find 保持一致
        find: https://motor.readthedocs.io/en/stable/api-asyncio/asyncio_motor_collection.html#motor.motor_asyncio.AsyncIOMotorCollection.find
        https://pymongo.readthedocs.io/en/3.12.0/api/pymongo/collection.html#pymongo.collection.Collection.find
        max_time_ms（可选）： max_time_ms 的值可以指定为**kwargs 的一部分，例如：
        return: motor.motor_asyncio.AsyncIOMotorCursor
        注意：注意find不需要await表达式，因为 find只创建一个 MotorCursor而不在服务器上执行任何操作。 MotorCursor方法，例如to_list() 执行实际操作。
        """
        results = mongo_conn.find(filter, *args, **kwargs)
        result = await results.to_list(None)
        return result

    @classmethod
    @func_time(callback)
    async def replace_one(cls, mongo_conn, filter, replacement, upsert=False, *args, **kwargs):
        """
        replace_one会完全替换文档，只有主id不变，而不是融合文档
        mongo_conn: 是到表级别的连接
        filter: 是查询条件
        replacement: 替换成该文档
        该UPSERT选项可以使用，如果匹配的文件不存在，插入一个新的文件：
        https://motor.readthedocs.io/en/stable/api-asyncio/asyncio_motor_collection.html#motor.motor_asyncio.AsyncIOMotorCollection.replace_one
        return: pymongo.results.UpdateResult
        https://pymongo.readthedocs.io/en/3.12.0/api/pymongo/results.html#pymongo.results.UpdateResult
        提示: 巨杉兼容mongodb协议的updateOne/deleteOne 只能在单个分区、单个子表上执行。replace_one使用了updateOne
        """
        result = await mongo_conn.replace_one(filter, replacement, upsert, *args, **kwargs)
        print('matched %d, modified %d' % (result.matched_count, result.modified_count))
        return result

    @classmethod
    @func_time(callback)
    async def insert_one(cls, mongo_conn, document, *args, **kwargs):
        """
        mongo_conn: 是到表级别的连接
        document: 要插入的文档
        https://motor.readthedocs.io/en/stable/api-asyncio/asyncio_motor_collection.html#motor.motor_asyncio.AsyncIOMotorCollection.insert_one
        return: pymongo.results.InsertOneResult
        """
        result = await mongo_conn.insert_one(document, *args, **kwargs)
        print('result %s' % repr(result.inserted_id))
        return result

    @classmethod
    @func_time(callback)
    async def insert_many(cls, mongo_conn, list_document, ordered=False, *args, **kwargs):
        """
        mongo_conn: 是到表级别的连接
        list_document: 要插入的文档的列表
        ordered:如果True（默认）文档将按提供的顺序连续插入服务器。如果发生错误，所有剩余的插入都将中止。
        如果False，文档将在服务器上以任意顺序插入，可能是并行的，并且将尝试所有文档插入。
        https://motor.readthedocs.io/en/stable/api-asyncio/asyncio_motor_collection.html#motor.motor_asyncio.AsyncIOMotorCollection.insert_one
        return: pymongo.results.InsertOneResult
        """
        result = await mongo_conn.insert_many(list_document, ordered=ordered, *args, **kwargs)
        # print('result %s' % repr(result.inserted_ids))
        return result

    @classmethod
    @func_time(callback)
    async def delete_many(cls, mongo_conn, filter, *args, **kwargs):
        """
        mongo_conn: 是到表级别的连接
        filter : 要删除的文档条件
        https://motor.readthedocs.io/en/stable/api-asyncio/asyncio_motor_collection.html?highlight=delete_many#motor.motor_asyncio.AsyncIOMotorCollection.delete_many
        return: pymongo.results.DeleteResult
        """
        result = await mongo_conn.delete_many(filter, *args, **kwargs)
        print(result.deleted_count)
        return result

    @classmethod
    @func_time(callback)
    async def update_many(cls, mongo_conn, filter, update, upsert=False, *args, **kwargs):
        """
        mongo_conn: 是到表级别的连接
        filter : 要更新的文档条件
        https://motor.readthedocs.io/en/stable/api-asyncio/asyncio_motor_collection.html?highlight=update#motor.motor_asyncio.AsyncIOMotorCollection.update_many
        return: pymongo.results.UpdateResult
        说明: 巨杉数据库无法使用update_many，且不会报错，但无法更新
        """
        result = await mongo_conn.update_many(filter, update, upsert, *args, **kwargs)
        print('matched %d, modified %d' % (result.matched_count, result.modified_count))
        return result

    @classmethod
    @func_time(callback)
    async def replace_one_substitute(cls, mongo_conn, query, insert_doc, upsert=False, *args, **kwargs):
        """
        client:连接对象

        巨杉数据库调用该方法，注意 query的条件必须要是分区键
        """
        # await cls.delete_many(mongo_conn, query)
        # insert_doc.update(query)
        # result = await cls.insert_one(mongo_conn, insert_doc)
        # return result
        # 巨杉的replace需要这么用
        insert_doc = {"$set": insert_doc}
        result = await mongo_conn.update_one(query, insert_doc, upsert, *args, **kwargs)
        return result

    @classmethod
    @func_time(callback)
    async def save_gt_16M_to_minio(cls, id_, save_dict):
        from settings import get_settings
        from app_main import app
        sets = get_settings()
        save_cnt = 0
        is_save_success = False
        exc = Exception("保存文件失败" + id_)
        while save_cnt < sets.MONGO_SAVE_TRY_CNT:
            try:
                save_path = sets.MINIO_GT_16M_DIR + "{}.big_json".format(id_)
                bytesIO = BytesIO((json.dumps(save_dict, ensure_ascii=False).encode("utf-8")))
                async with app.state.aioboto3.client('s3', endpoint_url=sets.AWS_LOCAL_ENDPOINT_URL) as s3:
                    await s3.upload_fileobj(bytesIO, sets.AWS_LOCAL_BUCKET, save_path)
                    chk_one = await s3.head_object(Bucket=sets.AWS_LOCAL_BUCKET, Key=save_path)
                    if chk_one != None:
                        is_save_success = True
                        break
                save_cnt += 1
            except:
                # print(traceback.format_exc())
                save_cnt += 1
                exc = sys.exc_info()[0]
        if not is_save_success:
            raise exc
        return True, save_path

    @classmethod
    @func_time(callback)
    async def save_gt_16M_to_file(cls, id_, save_dict):
        from settings import get_settings
        sets = get_settings()
        save_cnt = 0
        is_save_success = False
        exc = Exception("保存文件失败" + id_)
        while save_cnt < sets.MONGO_SAVE_TRY_CNT:
            try:
                save_path = BaseFile.get_new_path(sets.MONGO_BASE_DIR, sets.MONGO_SAVE_PATH)
                BaseDir.create_dir(save_path)
                save_path = BaseFile.get_new_path(save_path, "{}.big_json".format(id_))
                BaseFile.single_write_file(save_path, json.dumps(save_dict, ensure_ascii=False))
                if BaseFile.is_file_exists(save_path):
                    is_save_success = True
                    break
                save_cnt += 1
            except:
                save_cnt += 1
                exc = sys.exc_info()[0]

        if not is_save_success:
            raise exc
        return True, save_path
